///////////////////////////////////////////////////////////////////////////////////////////////////////
//	Copyright (c) 2010, Blogagic (http://blogagic.com/)
//	All rights reserved.
//
//	Redistribution and use in source and binary forms, with or without modification, 
//	are permitted provided that the following conditions are met:
//
//		- Redistributions of source code must retain the above copyright notice, this list of 
//		  conditions and the following disclaimer.
//		- Redistributions in binary form must reproduce the above copyright notice, this list 
//		  of conditions and the following disclaimer in the documentation and/or other materials 
//		  provided with the distribution.
//
//	THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR 
//	IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 
//	FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 
//	CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
//	DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 
//	DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER 
//	IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 
//	OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
///////////////////////////////////////////////////////////////////////////////////////////////////////

/*
	SparklineChart - Base class for Sparkline charts.
	Copyright (C) 2010  Blogagic (http://blogagic.com/)
	Version 1.1

	Licensed under the Creative Commons BSD Licence
	http://creativecommons.org/licenses/BSD/
*/
package com.blogagic.controls.sparklineClasses
{
	import com.blogagic.core.IDisposable;
	
	import flash.events.Event;
	import flash.geom.Point;
	
	import mx.collections.ArrayCollection;
	import mx.collections.ICollectionView;
	import mx.collections.IList;
	import mx.collections.ListCollectionView;
	import mx.controls.AdvancedDataGridBaseEx;
	import mx.controls.DataGrid;
	import mx.controls.dataGridClasses.DataGridListData;
	import mx.controls.listClasses.BaseListData;
	import mx.controls.listClasses.IDropInListItemRenderer;
	import mx.controls.listClasses.IListItemRenderer;
	import mx.core.UIComponent;
	import mx.events.CollectionEvent;
	import mx.events.ToolTipEvent;
	import mx.formatters.NumberFormatter;
	import mx.styles.CSSStyleDeclaration;
	import mx.styles.StyleManager;
	import mx.utils.ObjectUtil;
	
	/**
	 *  Color for the chart background.
	 * 
	 *  @default 0xCCCCCC
	 */
	[Style(name="backgroundColor", type="Color", format="Color", inherit="no")]
	
	/**
	 *  Transparency for the chart background.
	 * 
	 *  @default 0
	 */
	[Style(name="backgroundAlpha", type="Number", inherit="no")]
	
	/**
	 *  Padding around the chart area.
	 * 
	 *  @default 1
	 */
	[Style(name="padding", type="Number", inherit="no")]
	
	/**
	 *  SparklineChart is the base class for Sparkline chart components.
	 *  It is not used directly in applications.
	 *  
	 *  @mxml
	 *
	 *  <p>The SparklineChart class inherits all the properties and styles of its parent 
	 *  classes and adds the following:</p>
	 *
	 *  <pre>
	 *  &lt;mx:SparklineChart
	 *    <b>Properties</b>
	 *    dataProvider="null"
	 *    maxPlots="20"
	 *    xField=""
	 *    yField=""
	 *    showThreshold="false"
	 *    threshold="50"
	 *    upperIsFault="true"
	 *    xMinScaleMode="AUTOMATIC_SCALE"
	 *    xMaxScaleMode="AUTOMATIC_SCALE"
	 *    yMinScaleMode="AUTOMATIC_SCALE"
	 *    yMaxScaleMode="AUTOMATIC_SCALE"
	 *    xMinFixed="NaN"
	 *    xMaxFixed="NaN"
	 *    yMinFixed="NaN"
	 *    yMaxFixed="NaN"
	 *    showToolTip="true"
	 *    toolTipFunction="null"
	 *    toolTipNumberFormatter="null"
	 *    toolTipHeader=""
	 *    toolTipValueHeader="Last value: "
	 *    toolTipThresholdHeader="Threshold: "
	 * 
	 *    <b>Styles</b>
	 *    backgroundColor="0xCCCCCC"
	 *    backgroundAlpha="0"
	 *    padding="1"
	 * 
	 *    <b>Events</b>
	 *    showThresholdChanged="<i>No default</i>"
	 *    thresholdChanged="<i>No default</i>"
	 *    upperIsFaultChanged="<i>No default</i>"
	 *    xFieldChanged="<i>No default</i>"
	 *    yFieldChanged="<i>No default</i>"
	 *    maxPlotsChanged="<i>No default</i>"
	 *    xMinScaleModeChanged="<i>No default</i>"
	 *    xMaxScaleModeChanged="<i>No default</i>"
	 *    yMinScaleModeChanged="<i>No default</i>"
	 *    yMaxScaleModeChanged="<i>No default</i>"
	 *    xMinFixedChanged="<i>No default</i>"
	 *    xMaxFixedChanged="<i>No default</i>"
	 *    yMinFixedChanged="<i>No default</i>"
	 *    yMaxFixedChanged="<i>No default</i>"
	 *    showToolTipChanged="<i>No default</i>"
	 *    valueMinChanged="<i>No default</i>"
	 *    valueMaxChanged="<i>No default</i>"
	 *  /&gt;
	 *  </pre>
	 *
	 *  @author JYC
	 */
	public class SparklineChart extends UIComponent
		implements IListItemRenderer, IDropInListItemRenderer, IDisposable
	{
		/**
		 *  Indicates that the scale is automatically computed based on rendered data. 
		 *	Used when setting <code>xMinScaleMode</code>, <code>xMaxScaleMode</code>, <code>yMinScaleMode</code> or
		 *  <code>yMaxScaleMode</code>.
		 */
		public const AUTOMATIC_SCALE:String = "AutomaticScale";

		/**
		 *  Indicates that the scale is not computed using rendered data but predefined. 
		 *	Used when setting <code>xMinScaleMode</code>, <code>xMaxScaleMode</code>, <code>yMinScaleMode</code> or
		 *  <code>yMaxScaleMode</code>.
		 */
		public const FIXED_SCALE:String = "FixedScale";
		
		//--------------------------------------------------------------------------
		//
		//  Properties
		//
		//--------------------------------------------------------------------------
		
		//----------------------------------
		//  Data provider
		//----------------------------------
		
		/**
		 *  @private
		 */
		private var _dataProvider:ICollectionView;	
		[Inspectable(category="Data", type="mx.collections.ICollectionView", name="dataProvider")]
		
		/**
		 *  Data provider for the sparkline chart.
		 * 
		 *  When used as an itemRenderer, the dataProvider is automatically
		 *  populated from <code>data</code> or <code>listData</code> properties.
		 * 
		 *  @default null
		 */	
		public function get dataProvider():Object
		{
			return _dataProvider;
		}
		
		/**
		 *  @private
		 */
		public function set dataProvider(value:Object):void
		{
			if (_dataProvider != value) {
				if (_dataProvider)
				{
					_dataProvider.removeEventListener(CollectionEvent.COLLECTION_CHANGE, dataProviderChangeHandler);
				}
				
				if (value is Array)
				{
					value = new ArrayCollection(value as Array);
				}
				else if (value is ICollectionView)
				{
				}
				else if (value is IList)
				{
					value = new ListCollectionView(IList(value));
				}
				else if (value)
				{
					value = new ArrayCollection([value]);
				}
				else
				{
					value = new ArrayCollection();
				}
				
				_dataProvider = ICollectionView(value);
				
				if (_dataProvider)
				{
					_dataProvider.addEventListener(CollectionEvent.COLLECTION_CHANGE, dataProviderChangeHandler);
				}
				
				if (yMinScaleMode == AUTOMATIC_SCALE || yMaxScaleMode == AUTOMATIC_SCALE ||
					xMinScaleMode == AUTOMATIC_SCALE || xMaxScaleMode == AUTOMATIC_SCALE) {
					_rangeDirty = true;
					invalidateProperties();
				}
				_nbItemsDirty = true;
				invalidateDisplayList();
			}
		}
		
		//----------------------------------
		//  Data
		//----------------------------------
		
		/**
		 *  @private
		 */
		private var _data:Object;
		[Inspectable(environment="none")]
		
		/**
		 *  Sparkline data when SparklineChart is used as an item renderer or
		 *  item editor. 
		 * 
		 *  When used as an item renderer in a DataGrid, the Flex framework 
		 *  will automatically initialize the <code>data</code> property with the
		 *  item in the data provider this component should render.
		 * 
		 * <p>Note: Do not set <code>data</code> in MXML.</p>
		 *
		 * @default null
		 */
		[Bindable]
		public function get data():Object
		{
			return _data;
		}
		
		/**
		 *  @private
		 */
		public function set data(value:Object):void
		{
			_data = value;
			
			if (_listData &&
				_listData is DataGridListData &&
				DataGridListData(_listData).dataField)
			{
				dataProvider = _data[DataGridListData(_listData).dataField];
				
				// When used as an itemRenderer in a DataGrid, if yField is defined, we need
				// a custom sort function.
				setSortFunction();
			}
			else
			{
				dataProvider = _data;
			}
		}
		
		//----------------------------------
		//  ListData
		//----------------------------------
		
		/**
		 *  @private
		 */
		private var _listData:BaseListData;
		[Inspectable(environment="none")]
		
		/**
		 *  Sparkline data when SparklineChart is used as a drop-in item renderer
		 *  or drop-in item editor. 
		 * 
		 *  When used as a drop-in item renderer or drop-in item editor, the
		 *  Flex framework will automatically initialize the <code>listData</code>
		 *  property with the additional data from the list control.
		 *
		 *  <p>Note: Do not set <code>listData</code> in MXML.</p>
		 *
		 *  @default null
		 */
		[Bindable]
		public function get listData():BaseListData
		{
			return _listData;
		}
		
		/**
		 *  @private
		 */
		public function set listData(value:BaseListData):void
		{
			_listData = value;
		}
		
		//----------------------------------
		//  Show Threshold
		//----------------------------------
		
		/**
		 *  @private
		 */
		private var _showThreshold:Boolean = false;
		[Inspectable(category="Threshold", type="Boolean", defaultValue="false")]
		
		/**
		 *  A flag that indicates whether threshold must be shown.
		 * 
		 *  <p>Threshold display depends on the type of chart but,
		 *  basically, it allows comparing values to a reference
		 *  (the threshold) and highlighting in some way the comparison
		 *  result.</p>
		 * 
		 *  <p>This property can be used as the source for data binding. When this
		 *  property is modified, it dispatches the <code>showThresholdChanged</code> event.</p>
		 * 
		 *  <p>Note: This property is automatically set when defining
		 *  the <code>threshold</code> property.</p>
		 *
		 * @default false
		 */
		[Bindable("showThresholdChanged")]
		public function get showThreshold():Boolean
		{
			return _showThreshold;
		}
		
		/**
		 *  @private
		 */
		public function set showThreshold(value:Boolean):void
		{
			if (_showThreshold != value)
			{
				_showThreshold = value;
				invalidateDisplayList();
				
				dispatchEvent(new Event("showThresholdChanged"));
			}
		}
		
		//----------------------------------
		//  Threshold
		//----------------------------------
		
		/**
		 *  @private
		 */
		private var _threshold:Number = 50;
		[Inspectable(category="Threshold", type="Number", defaultValue="50")]
		
		/**
		 *  Number that specifies the threshold value.
		 * 
		 *  <p>Threshold usage depends on the type of chart but,
		 *  basically, it allows comparing chart values to the 
		 *  threshold to highlight in some way the comparison
		 *  result.</p>
		 *
		 *  <p>This property can be used as the source for data binding. When this
		 *  property is modified, it dispatches the <code>thresholdChanged</code> event.</p>
		 * 
		 *  <p>Note: When defining <code>threshold</code>, the <code>
		 *  showThreshold</code> is automatically set to <code>true</code>.</p>
		 *  
		 *  @default 50
		 */
		[Bindable("thresholdChanged")]
		public function get threshold():Number
		{
			return _threshold;
		}
		
		/**
		 *  @private
		 */
		public function set threshold(value:Number):void
		{
			if (_threshold != value)
			{
				_threshold = value;
				showThreshold = true;
				invalidateDisplayList();
				
				dispatchEvent(new Event("thresholdChanged"));
			}
		}
		
		//----------------------------------
		//  Upper is Fault
		//----------------------------------
		
		/**
		 *  @private
		 */
		private var _upperIsFault:Boolean = true;
		[Inspectable(category="Threshold", type="Boolean", defaultValue="true")]
		
		/**
		 *  A flag that specifies whether a value bigger than the threshold is considered
		 *  as a good or a bad value.
		 * 
		 *  <p>When <code>upperIsFault</code> is <code>true</code>, a value
		 *  bigger than the threshold is considered as bad.</p>
		 *  <p>When <code>upperIsFault</code> is <code>false</code>, a value
		 *  bigger than the threshold is considered as good.</p>
		 *  
		 *  <p>This property can be used as the source for data binding. When this
		 *  property is modified, it dispatches the <code>upperIsFaultChanged</code> event.</p>
		 * 
		 * @default true
		 */
		[Bindable("upperIsFaultChanged")]
		public function get upperIsFault():Boolean
		{
			return _upperIsFault;
		}
		
		/**
		 *  @private
		 */
		public function set upperIsFault(value:Boolean):void
		{
			if (_upperIsFault != value)
			{
				_upperIsFault = value;
				invalidateDisplayList();
				
				dispatchEvent(new Event("upperIsFaultChanged"));
			}
		}
		
		//----------------------------------
		//  xField
		//----------------------------------
		
		/**
		 *  @private
		 */
		private var _xField:String = "";
		[Inspectable(category="Data", type="String", defaultValue="")]
		
		/**
		 *  The name of the field or property in the data provider item associated with 
		 *  the x-axis value of each sparkline point.
		 * 
		 *  <p>When <code>xField</code> is not defined, the considered x-axis
		 *  value is the element index.</p>
		 *  
		 *  <p>This property can be used as the source for data binding. When this
		 *  property is modified, it dispatches the <code>xFieldChanged</code> event.</p>
		 * 
		 * @default ""
		 */
		[Bindable("xFieldChanged")]
		public function get xField():String
		{
			return _xField;
		}
		
		/**
		 *  @private
		 */
		public function set xField(value:String):void
		{
			if (_xField != value)
			{
				_xField = value;
				_rangeDirty = true;
				invalidateProperties();
				invalidateDisplayList();
				
				dispatchEvent(new Event("xFieldChanged"));
			}
		}
		
		//----------------------------------
		//  yField
		//----------------------------------
		
		/**
		 *  @private
		 */
		private var _yField:String = "";
		[Inspectable(category="Data", type="String", defaultValue="")]
		
		/**
		 *  The name of the field or property in the data provider item associated with 
		 *  the y-axis value of each sparkline point.
		 * 
		 *  <p>When <code>yField</code> is not defined, the considered y-axis
		 *  value is the element itself.</p>
		 *  
		 *  <p>This property can be used as the source for data binding. When this
		 *  property is modified, it dispatches the <code>yFieldChanged</code> event.</p>
		 * 
		 * @default ""
		 */
		[Bindable("yFieldChanged")]
		public function get yField():String
		{
			return _yField;
		}
		
		/**
		 *  @private
		 */
		public function set yField(value:String):void
		{
			if (_yField != value)
			{
				_yField = value;
				_rangeDirty = true;
				invalidateProperties();
				invalidateDisplayList();
				
				// When used as an itemRenderer in a DataGrid, if yField is defined, we need
				// a custom sort function.
				setSortFunction();
				
				dispatchEvent(new Event("yFieldChanged"));
			}
		}
		
		//----------------------------------
		//  maxPlots
		//----------------------------------
		
		/**
		 *  @private
		 */
		private var _maxPlots:Number = 20;
		[Inspectable(category="Data", type="Number", defaultValue="20")]
		
		/**
		 *  The maximum number of sparkline points to plot.
		 * 
		 *  <p>Only the latest <code>maxPlots</code> values available from
		 *  the <code>dataProvider</code> are considered when drawing the 
		 *  sparkline chart.</p>
		 *  
		 *  <p>This property can be used as the source for data binding. When this
		 *  property is modified, it dispatches the <code>maxPlotsChanged</code> event.</p>
		 * 
		 * @default "20"
		 */
		[Bindable("maxPlotsChanged")]
		public function get maxPlots():Number
		{
			return _maxPlots;
		}
		
		/**
		 *  @private
		 */
		public function set maxPlots(value:Number):void
		{
			if (_maxPlots != value)
			{
				_maxPlots = value;
				_rangeDirty = true;
				_horizontalScaleDirty = true;
				_verticalScaleDirty = true;
				_nbItemsDirty = true;
				invalidateProperties();
				invalidateDisplayList();
				
				dispatchEvent(new Event("maxPlotsChanged"));
			}
		}
		
		//----------------------------------
		//  xMinScaleMode
		//----------------------------------
		
		/**
		 *   @private
		 */
		private var _xMinScaleMode:String = AUTOMATIC_SCALE;
		[Inspectable(category="Data", enumeration="AutomaticScale,FixedScale", type="String", defaultValue="AutomaticScale")]
		
		/**
		 *  Specifies how the minimum value of the x-axis is determined.
		 * 
		 *  <p>If set to <code>AUTOMATIC_SCALE</code>, the minimum value for
		 *  the x-axis is automatically computed based on the data used to
		 *  draw the sparkline chart.</p>
		 *  <p>If set to <code>FIXED_SCALE</code>, the minimum value for the
		 *  x-axis is the <code>xMinFixed</code> property.</p>
		 * 
		 *  <p>Note: This property is automatically set to <code>FIXED_SCALE</code>
		 *  when setting the <code>xMinFixed</code> property.</p>
		 *  
		 *  <p>This property can be used as the source for data binding. When this
		 *  property is modified, it dispatches the <code>xMinScaleModeChanged</code> event.</p>
		 * 
		 *  @default AUTOMATIC_SCALE
		 */
		[Bindable("xMinScaleModeChanged")]
		public function get xMinScaleMode():String
		{
			return _xMinScaleMode;
		}
		
		/**
		 *  @private
		 */
		public function set xMinScaleMode(value:String):void
		{
			if (_xMinScaleMode != value)
			{
				_xMinScaleMode = value;
				_rangeDirty = true;
				invalidateProperties();
				invalidateDisplayList();
				
				dispatchEvent(new Event("xMinScaleModeChanged"));
			}
		}
		
		//----------------------------------
		//  xMaxScaleMode
		//----------------------------------
		
		/**
		 *  @private
		 */
		private var _xMaxScaleMode:String = AUTOMATIC_SCALE;
		[Inspectable(category="Data", enumeration="AutomaticScale,FixedScale", type="String", defaultValue="AutomaticScale")]
		
		/**
		 *  Specifies how the maximum value of the x-axis is determined.
		 * 
		 *  <p>If set to <code>AUTOMATIC_SCALE</code>, the maximum value for
		 *  the x-axis is automatically computed based on the data used to
		 *  draw the sparkline chart.</p>
		 *  <p>If set to <code>FIXED_SCALE</code>, the maximum value for the
		 *  x-axis is the <code>xMaxFixed</code> property.</p>
		 * 
		 *  <p>Note: This property is automatically set to <code>FIXED_SCALE</code>
		 *  when setting the <code>xMaxFixed</code> property.</p>
		 *  
		 *  <p>This property can be used as the source for data binding. When this
		 *  property is modified, it dispatches the <code>xMaxScaleModeChanged</code> event.</p>
		 * 
		 *  @default AUTOMATIC_SCALE
		 */
		[Bindable("xMaxScaleModeChanged")]
		public function get xMaxScaleMode():String
		{
			return _xMaxScaleMode;
		}
		
		/**
		 *  @private
		 */
		public function set xMaxScaleMode(value:String):void
		{
			if (_xMaxScaleMode != value)
			{
				_xMaxScaleMode = value;
				_rangeDirty = true;
				invalidateProperties();
				invalidateDisplayList();
				
				dispatchEvent(new Event("xMaxScaleModeChanged"));
			}
		}
		
		//----------------------------------
		//  yMinScaleMode
		//----------------------------------
		
		/**
		 *  @private
		 */
		private var _yMinScaleMode:String = AUTOMATIC_SCALE;
		[Inspectable(category="Data", enumeration="AutomaticScale,FixedScale", type="String", defaultValue="AutomaticScale")]
		
		/**
		 *  Specifies how the minimum value of the y-axis is determined.
		 * 
		 *  <p>If set to <code>AUTOMATIC_SCALE</code>, the minimum value for
		 *  the y-axis is automatically computed based on the data used to
		 *  draw the sparkline chart.</p>
		 *  <p>If set to <code>FIXED_SCALE</code>, the minimum value for the
		 *  y-axis is the <code>yMinFixed</code> property.</p>
		 * 
		 *  <p>Note: This property is automatically set to <code>FIXED_SCALE</code>
		 *  when setting the <code>yMinFixed</code> property.</p>
		 *  
		 *  <p>This property can be used as the source for data binding. When this
		 *  property is modified, it dispatches the <code>yMinScaleModeChanged</code> event.</p>
		 * 
		 *  @default AUTOMATIC_SCALE
		 */
		[Bindable("yMinScaleModeChanged")]
		public function get yMinScaleMode():String
		{
			return _yMinScaleMode;
		}
		
		/**
		 *  @private
		 */
		public function set yMinScaleMode(value:String):void
		{
			if (_yMinScaleMode != value)
			{
				_yMinScaleMode = value;
				_rangeDirty = true;
				invalidateProperties();
				invalidateDisplayList();
				
				dispatchEvent(new Event("yMinScaleModeChanged"));
			}
		}
		
		//----------------------------------
		//  yMaxScaleMode
		//----------------------------------
		
		/**
		 *  @private
		 */
		private var _yMaxScaleMode:String = AUTOMATIC_SCALE;
		[Inspectable(category="Data", enumeration="AutomaticScale,FixedScale", type="String", defaultValue="AutomaticScale")]
		
		/**
		 *  Specifies how the maximum value of the y-axis is determined.
		 * 
		 *  <p>If set to <code>AUTOMATIC_SCALE</code>, the maximum value for
		 *  the y-axis is automatically computed based on the data used to
		 *  draw the sparkline chart.</p>
		 *  <p>If set to <code>FIXED_SCALE</code>, the maximum value for the
		 *  y-axis is the <code>yMaxFixed</code> property.</p>
		 * 
		 *  <p>Note: This property is automatically set to <code>FIXED_SCALE</code>
		 *  when setting the <code>yMaxFixed</code> property.</p>
		 *  
		 *  <p>This property can be used as the source for data binding. When this
		 *  property is modified, it dispatches the <code>yMaxScaleModeChanged</code> event.</p>
		 * 
		 *  @default AUTOMATIC_SCALE
		 */
		[Bindable("yMaxScaleModeChanged")]
		public function get yMaxScaleMode():String
		{
			return _yMaxScaleMode;
		}
		
		/**
		 *  @private
		 */
		public function set yMaxScaleMode(value:String):void
		{
			if (_yMaxScaleMode != value)
			{
				_yMaxScaleMode = value;
				_rangeDirty = true;
				invalidateProperties();
				invalidateDisplayList();
				
				dispatchEvent(new Event("yMaxScaleModeChanged"));
			}
		}
		
		//----------------------------------
		//  xMinFixed
		//----------------------------------
		
		/**
		 *  @private
		 */
		private var _xMinFixed:Number;
		[Inspectable(category="Data", type="Number", defaultValue="")]
		
		/**
		 *  Specifies minimum value of the x-axis.
		 * 
		 *  <p>Any value lesser than <code>xMinFixed</code> will be clipped when rendered.</p>
		 * 
		 *  <p>Note: When defining <code>xMinFixed</code>, the <code>xMinScaleMode</code> 
		 *  property is automatically set to <code>FIXED_SCALE</code>.</p>
		 *  
		 *  <p>This property can be used as the source for data binding. When this
		 *  property is modified, it dispatches the <code>xMinFixedChanged</code> event.</p>
		 * 
		 *  @default NaN
		 */
		[Bindable("xMinFixedChanged")]
		public function get xMinFixed():Number
		{
			return _xMinFixed;
		}
		
		/**
		 *  @private
		 */
		public function set xMinFixed(value:Number):void
		{
			if (_xMinFixed != value)
			{
				_xMinFixed = value;
				xMinScaleMode = FIXED_SCALE;
				_rangeDirty = true;
				invalidateProperties();
				
				dispatchEvent(new Event("xMinFixedChanged"));
			}
		}
		
		//----------------------------------
		//  xMaxFixed
		//----------------------------------
		
		/**
		 *  @private
		 */
		private var _xMaxFixed:Number;
		[Inspectable(category="Data", type="Number", defaultValue="")]
		
		/**
		 *  Specifies maximum value of the x-axis.
		 * 
		 *  <p>Any value greater than <code>xMaxFixed</code> will be clipped when rendered.</p>
		 * 
		 *  <p>Note: When defining <code>xMaxFixed</code>, the <code>xMaxScaleMode</code> 
		 *  property is automatically set to <code>FIXED_SCALE</code>.</p>
		 *  
		 *  <p>This property can be used as the source for data binding. When this
		 *  property is modified, it dispatches the <code>xMaxFixedChanged</code> event.</p>
		 * 
		 *  @default NaN
		 */
		[Bindable("xMaxFixedChanged")]
		public function get xMaxFixed():Number
		{
			return _xMaxFixed;
		}
		
		/**
		 *  @private
		 */
		public function set xMaxFixed(value:Number):void
		{
			if (_xMaxFixed != value)
			{
				_xMaxFixed = value;
				xMaxScaleMode = FIXED_SCALE;
				_rangeDirty = true;
				invalidateProperties();
				
				dispatchEvent(new Event("xMaxFixedChanged"));
			}
		}     
		
		//----------------------------------
		//  yMinFixed
		//----------------------------------
		
		/**
		 *  @private
		 */
		private var _yMinFixed:Number;
		[Inspectable(category="Data", type="Number", defaultValue="")]
		
		/**
		 *  Specifies minimum value of the y-axis.
		 * 
		 *  <p>Any value lesser than <code>yMinFixed</code> will be clipped when rendered.</p>
		 * 
		 *  <p>Note: When defining <code>yMinFixed</code>, the <code>yMinScaleMode</code> 
		 *  property is automatically set to <code>FIXED_SCALE</code>.</p>
		 *  
		 *  <p>This property can be used as the source for data binding. When this
		 *  property is modified, it dispatches the <code>yMinFixedChanged</code> event.</p>
		 * 
		 *  @default NaN
		 */
		[Bindable("yMinFixedChanged")]
		public function get yMinFixed():Number
		{
			return _yMinFixed;
		}
		
		/**
		 *  @private
		 */
		public function set yMinFixed(value:Number):void
		{
			if (_yMinFixed != value)
			{
				_yMinFixed = value;
				yMinScaleMode = FIXED_SCALE;
				_rangeDirty = true;
				invalidateProperties();
				
				dispatchEvent(new Event("yMinFixedChanged"));
			}
		}
		
		//----------------------------------
		//  yMaxFixed
		//----------------------------------
		
		/**
		 *  @private
		 */
		private var _yMaxFixed:Number;
		[Inspectable(category="Data", type="Number", defaultValue="")]
		
		/**
		 *  Specifies maximum value of the y-axis.
		 * 
		 *  <p>Any value greater than <code>yMaxFixed</code> will be clipped when rendered.</p>
		 * 
		 *  <p>Note: When defining <code>yMaxFixed</code>, the <code>yMaxScaleMode</code> 
		 *  property is automatically set to <code>FIXED_SCALE</code>.</p>
		 *  
		 *  <p>This property can be used as the source for data binding. When this
		 *  property is modified, it dispatches the <code>yMaxFixedChanged</code> event.</p>
		 * 
		 *  @default NaN
		 */
		[Bindable("yMaxFixedChanged")]
		public function get yMaxFixed():Number
		{
			return _yMaxFixed;
		}
		
		/**
		 *  @private
		 */
		public function set yMaxFixed(value:Number):void
		{
			if (_yMaxFixed != value)
			{
				_yMaxFixed = value;
				yMaxScaleMode = FIXED_SCALE;
				_rangeDirty = true;
				invalidateProperties();
				
				dispatchEvent(new Event("yMaxFixedChanged"));
			}
		}
		
		//----------------------------------
		//  ToolTip
		//----------------------------------
		
		/**
		 *  @private
		 */
		private var _showToolTip:Boolean = true;
		[Inspectable(category="ToolTip", type="Boolean", defaultValue="true")]
		
		/**
		 *  Flag that indicates whether a toolTip must be shown when hovering over
		 *  the sparkline chart.
		 * 
		 *  <p>This property can be used as the source for data binding. When this
		 *  property is modified, it dispatches the <code>showToolTipChanged</code> event.</p>
		 * 
		 *  @default true
		 */
		[Bindable("showToolTipChanged")]
		public function get showToolTip():Boolean
		{
			return _showToolTip;
		}
		
		/**
		 *  @private
		 */
		public function set showToolTip(value:Boolean):void
		{
			if (_showToolTip != value)
			{
				_showToolTip = value;
				// Put a fake value, it will be correctly filled just before being
				// displayed (in the event handler for TOOL_TIP_SHOW event)
				if (_showToolTip)
					toolTip="fake";
				else
					toolTip="";
				_toolTipDirty = true;
				
				dispatchEvent(new Event("showToolTipChanged"));
			}
		}
		
		/**
		 *  User-supplied function to fill the toolTip content. If no function is specified, the
		 *  <code>defaultToolTipFunction</code> is used.
		 * 
		 *  <p>The function takes as arguments a reference to the SparklineChart and the last 
		 *  received value. It returns a string that will be used as the toolTip for the SparklineChart.</p> 
		 *   
		 *  <pre>
		 *  public function toolTipFunction(sparklineChart:SparklineChart, value:Number):String {
		 *      ...
		 *      return toolTipValue;
		 *  }
		 *  </pre>   
		 * 
		 *  @default null
		 */
		public var toolTipFunction:Function;
		[Inspectable(category="ToolTip", type="Function", defaultValue="null")]
		
		/**
		 *  An instance of a subclasses of mx.formatters.Formatter. The control use this class to format 
		 *  the data displayed in the default tooltip.
		 * 
		 *  <p>If <code>null</code>, the number is displayed without any specific formatting.</p>
		 * 
		 *  <p>Note: <code>toolTipNumberFormatter</code> is used only if <code>toolTipFunction</code> is not defined.</p>
		 *
		 *  @default null
		 */
		public var toolTipNumberFormatter:NumberFormatter;
		[Inspectable(category="ToolTip", type="NumberFormatter", defaultValue="null")]
		
		/**
		 *  Text for the header of the default toolTip.
		 *
		 *  <p>Note: <code>toolTipHeader</code> is used only if <code>toolTipFunction</code> is not defined.</p>
		 *
		 *  @default ""
		 */
		public var toolTipHeader:String = "";
		[Inspectable(category="ToolTip", type="String", defaultValue="")]
		
		/**
		 *  Text to display in front of the value in the default toolTip.
		 * 
		 *  <p>Note: <code>toolTipValueHeader</code> is used only if <code>toolTipFunction</code> is not defined.</p>
		 *
		 *  @default "Last value: "
		 */
		public var toolTipValueHeader:String = "Last value: ";
		[Inspectable(category="ToolTip", type="String", defaultValue="Last value: ")]
		
		/**
		 *  Text to display in front of the threshold value in the default toolTip.
		 *
		 * <p>Note: <code>toolTipThresholdHeader</code> is used only if <code>toolTipFunction</code> is not defined.</p>
		 *
		 * @default "Threshold: "
		 */
		public var toolTipThresholdHeader:String = "Threshold: ";
		[Inspectable(category="ToolTip", type="String", defaultValue="Threshold: ")]
		
		//----------------------------------
		//
		//  Constructor
		//
		//----------------------------------
		
		/**
		 * Constructor
		 */
		public function SparklineChart()
		{
			super();
			
			// Put a fake value as toolTip, it will be correctly updated just before being
			// displayed (in the event handler for TOOL_TIP_SHOW event)
			toolTip="fake";
			addEventListener(ToolTipEvent.TOOL_TIP_SHOW, toolTipShowHandler);
			addEventListener(ToolTipEvent.TOOL_TIP_HIDE, toolTipHideHandler);
		}	
		
		//--------------------------------------------------------------------------
		//
		//  Cleanup
		//
		//--------------------------------------------------------------------------
		
		/**
		 *  Use the dispose method to explicitly release unmanaged resources in conjunction 
		 *  with the garbage collector. 
		 *  Call this method when the control is no longer needed.
		 */
		public function dispose():void {
			if (_dataProvider) {
				_dataProvider.removeEventListener(CollectionEvent.COLLECTION_CHANGE, dataProviderChangeHandler);
				_dataProvider = null;
			}
			
			removeEventListener(ToolTipEvent.TOOL_TIP_SHOW, toolTipShowHandler);
			removeEventListener(ToolTipEvent.TOOL_TIP_HIDE, toolTipHideHandler);
			
			position = null;
			toolTipFunction = null;
		}
		
		//----------------------------------
		//
		//  Variables
		//
		//----------------------------------
		
		/**
		 *  @private
		 *  Dirty flag indicating if xMin, xMax, yMin and/or yMax need
		 *  to be updated (based on new data or scaling configuration
		 *  changes). 
		 */
		private var _rangeDirty:Boolean;
		
		/**
		 *  @private
		 *  Dirty flag indicating if showToolTip has changed. 
		 */
		private var _toolTipDirty:Boolean;
		
		/**
		 *  @private
		 *  Dirty flag indicating if nbItems need to be updated based
		 *  on chart configuration change (maxPlots) or new data.
		 */
		private var _nbItemsDirty:Boolean;
		
		/**
		 *  @private
		 *  Number of sparkline data points to be rendered.
		 */
		private var _nbItems:Number;
		
		/**
		 *  @private
		 */
		protected function get nbItems():Number
		{
			return _nbItems;
		}
		
		/**
		 *  @private
		 *  When the number of data points to be rendered changes,
		 *  horizontal scale must be updated.
		 */
		protected function set nbItems(value:Number):void
		{
			if (_nbItems != value)
			{
				_nbItems = value;
				_horizontalScaleDirty = true;
				invalidateProperties();
				invalidateDisplayList();
			}
		}
		
		/**
		 *  @private
		 */
		private var _nbItemsChanged:Boolean;
		
		/**
		 *  @private
		 */
		private var _posMinValue:Number;
		
		/**
		 *  Specifies the position of the minimum y-axis value.
		 */
		protected function get posMinValue():Number
		{
			return _posMinValue;
		}
		
		/**
		 *  @private
		 */
		private function set posMinValue(value:Number):void
		{
			if (_posMinValue != value)
			{
				_posMinValue = value;
			}
		}
		
		/**
		 *  @private
		 */
		private var _posMaxValue:Number;
		
		/**
		 *  Specifies the position of the maximum y-axis value.
		 */
		protected function get posMaxValue():Number
		{
			return _posMaxValue;
		}
		
		/**
		 *  @private
		 */
		private function set posMaxValue(value:Number):void
		{
			if (_posMaxValue != value)
			{
				_posMaxValue = value;
			}
		}
		
		/**
		 *  @private
		 */
		private var _xMin:Number;
		
		/**
		 *  Specifies the minimum value on the x-axis.
		 * 
		 *  <p>It can be either a computed value or the one provided
		 *  by setting the <code>xMinFixed</code> property.</p>
		 */
		protected function get xMin():Number
		{
			return _xMin;
		}
		
		/**
		 *  @private
		 *  When <code>xMin</code> is changed, 
		 *  horizontal scale must be updated.
		 */
		protected function set xMin(value:Number):void
		{
			if (_xMin != value)
			{
				_xMin = value;
				_horizontalScaleDirty = true;
				invalidateProperties();
				invalidateDisplayList();
			}
		}
		
		/**
		 *  @private
		 */
		private var _xMax:Number;
		
		/**
		 *  Specifies the maximum value on the x-axis.
		 * 
		 *  <p>It can be either a computed value or the one provided
		 *  by setting the <code>xMaxFixed</code> property.</p>
		 */
		protected function get xMax():Number
		{
			return _xMax;
		}
		
		/**
		 *  @private
		 *  When <code>xMax</code> is changed, 
		 *  horizontal scale must be updated.
		 */
		protected function set xMax(value:Number):void
		{
			if (_xMax != value)
			{
				_xMax = value;
				_horizontalScaleDirty = true;
				invalidateProperties();
				invalidateDisplayList();
			}
		}
		
		/**
		 *  @private
		 */
		private var _yMin:Number;
		
		/**
		 *  Specifies the minimum value on the y-axis.
		 * 
		 *  <p>It can be either a computed value or the one provided
		 *  by setting the <code>yMinFixed</code> property.</p>
		 */
		protected function get yMin():Number
		{
			return _yMin;
		}
		
		/**
		 *  @private
		 *  When <code>yMin</code> is changed, 
		 *  vertical scale must be updated.
		 */
		protected function set yMin(value:Number):void
		{
			if (_yMin != value)
			{
				_yMin = value;
				_verticalScaleDirty = true;
				invalidateProperties();
				invalidateDisplayList();
			}
		}
		
		/**
		 *  @private
		 */
		private var _yMax:Number;
		
		/**
		 *  Specifies the maximum value on the y-axis.
		 * 
		 *  <p>It can be either a computed value or the one provided
		 *  by setting the <code>yMaxFixed</code> property.</p>
		 */
		protected function get yMax():Number
		{
			return _yMax;
		}
		
		/**
		 *  @private
		 *  When <code>yMax</code> is changed, 
		 *  vertical scale must be updated.
		 */
		protected function set yMax(value:Number):void
		{
			if (_yMax != value)
			{
				_yMax = value;
				_verticalScaleDirty = true;
				invalidateProperties();
				invalidateDisplayList();
			}
		}
		
		/**
		 *  @private
		 */
		private var _valueMin:Number;
		
		/**
		 *  Specifies the minimum value from the dataProvider.
		 * 
		 *  <p>This property can be used as the source for data binding. When this
		 *  property is modified, it dispatches the <code>valueMinChanged</code> event.</p>
		 * 
		 *  <p>This property can be used as the source for data binding. When this
		 *  property is modified, it dispatches the <code>valueMinChanged</code> event.</p>
		 */
		[Bindable("valueMinChanged")]
		public function get valueMin():Number
		{
			return _valueMin;
		}
		
		/**
		 *  @private
		 */
		protected function set valueMin(value:Number):void
		{
			if (_valueMin != value)
			{
				_valueMin = value;
				_toolTipDirty = true;
				
				dispatchEvent(new Event("valueMinChanged"));
			}
		}
		
		/**
		 *  @private
		 */
		private var _valueMax:Number;
		
		/**
		 *  Specifies the maximum value from the dataProvider.
		 * 
		 *  <p>This property can be used as the source for data binding. When this
		 *  property is modified, it dispatches the <code>valueMaxChanged</code> event.</p>
		 * 
		 *  <p>This property can be used as the source for data binding. When this
		 *  property is modified, it dispatches the <code>valueMaxChanged</code> event.</p>
		 */
		[Bindable("valueMaxChanged")]
		public function get valueMax():Number
		{
			return _valueMax;
		}
		
		/**
		 *  @private
		 */
		protected function set valueMax(value:Number):void
		{
			if (_valueMax != value)
			{
				_valueMax = value;
				_toolTipDirty = true;
				
				dispatchEvent(new Event("valueMaxChanged"));
			}
		}
		
		/**
		 *  @private
		 *  Scaling factor to be applied to convert between data values and pixels
		 *  on the x-axis.
		 */
		protected var _horizontalScale:Number;
		
		/**
		 *  @private
		 *  Indicates if the scaling factor must be updated.
		 */
		protected var _horizontalScaleDirty:Boolean;
		
		/**
		 *  @private
		 *  Scaling factor to be applied to convert between data values and pixels
		 *  on the y-axis.
		 */
		protected var _verticalScale:Number;
		
		/**
		 *  @private
		 *  Indicates if the scaling factor must be updated.
		 */
		protected var _verticalScaleDirty:Boolean;
		
		/**
		 *  @private
		 *  Indicates our width last time updateDisplayList was called.
		 */
		private var _lastWidth:Number;
		
		/**
		 *  @private
		 *  Indicates our height last time updateDisplayList was called.
		 */
		private var _lastHeight:Number;
		
		//--------------------------------------------------------------------------
		//
		//  Styles
		//
		//--------------------------------------------------------------------------
		
		/**
		 *  @private
		 */
		private static var classConstructed:Boolean = classConstruct();
		
		/**
		 *  @private
		 *  Styles initialization.
		 */
		private static function classConstruct():Boolean {
			
			var style:CSSStyleDeclaration = StyleManager.getStyleDeclaration("SparklineChart");
			if (!style) {
				style = new CSSStyleDeclaration();
				
				style.defaultFactory = function():void {
					this.backgroundColor = 0xCCCCCC;
					this.backgroundAlpha = 0;
					this.padding = 1;
				};
				StyleManager.setStyleDeclaration("SparklineChart", style, true);
			}
			else {
				if (style.getStyle("backgroundColor") == undefined) {
					style.setStyle("backgroundColor", "0xCCCCCC");	        		
				}
				if (style.getStyle("backgroundAlpha") == undefined) {
					style.setStyle("backgroundAlpha", "0");	        		
				}
				if (style.getStyle("padding") == undefined) {
					style.setStyle("padding", "1");	        		
				}
			}
			
			return true;        
		}
		
		/**
		 *  @private
		 */
		override public function styleChanged(styleProp:String):void {
			super.styleChanged(styleProp);
			
			switch (styleProp) {
				case "backgroundColor":
				case "backgroundAlpha":
					invalidateDisplayList();
					break;
				case "padding":
					_horizontalScaleDirty = true;
					_verticalScaleDirty = true;
					invalidateProperties();
					invalidateDisplayList();
					break;
			}
		}
		
		//----------------------------------
		//
		//  Methods - Life Cycle
		//
		//----------------------------------
		
		/**
		 *  @private
		 */
		protected override function measure():void
		{
			measuredWidth = width;
			measuredHeight = height;	
		}
		
		/**
		 * @private
		 */
		override protected function commitProperties():void
		{
			if (_nbItemsDirty) {
				nbItems = Math.min(maxPlots, dataProvider.length);
				_nbItemsDirty = false;	
			}
			if (_rangeDirty) {
				updateRange();
				_rangeDirty = false;
			}
			super.commitProperties();
		}
		
		/**
		 * @private
		 */
		override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
		{
			super.updateDisplayList(unscaledWidth, unscaledHeight);
			
			graphics.clear();
			
			if (unscaledWidth == 0 && unscaledHeight == 0) {
				return;
			}
			
			drawBackground(unscaledWidth, unscaledHeight);
			
			if (!dataProvider)
				return;
			
			manageResize(unscaledWidth, unscaledHeight);
			
			updateRange();
			updateHorizontalScale(unscaledWidth);
			updateVerticalScale(unscaledHeight);
			
			drawChart(unscaledWidth, unscaledHeight);
			
			_toolTipDirty = true;
			updateToolTip();
		}
		
		//----------------------------------
		//
		//  Handlers
		//
		//----------------------------------
		
		/**
		 *  @private
		 *  When the data changes, rendering must be updated.
		 *  If scaling is automatic, it also needs to be updated.
		 */
		private function dataProviderChangeHandler(event:CollectionEvent):void
		{
			if (yMinScaleMode == AUTOMATIC_SCALE || yMaxScaleMode == AUTOMATIC_SCALE ||
				xMinScaleMode == AUTOMATIC_SCALE || xMaxScaleMode == AUTOMATIC_SCALE) {
				_rangeDirty = true;
			}
			_nbItemsDirty = true;
			invalidateProperties();
			
			invalidateDisplayList();
		} 
		
		//----------------------------------
		//
		//  Methods
		//
		//----------------------------------
		
		/**
		 *  Update xMin, xMax, yMin and yMax values based on scaling
		 *  policy and values to be rendered.
		 */
		protected function updateRange():void {
			if (!_dataProvider)
				return;
			
			if (_rangeDirty) {
				var xMinNew:Number;
				var xMaxNew:Number;
				var yMinNew:Number;
				var yMaxNew:Number;
				
				xMinNew = Number.POSITIVE_INFINITY;
				yMinNew = Number.POSITIVE_INFINITY;
				xMaxNew = Number.NEGATIVE_INFINITY;
				yMaxNew = Number.NEGATIVE_INFINITY;
				
				var xCoord:Number;
				var yCoord:Number;
				var i:uint = 0;
				var l:uint = dataProvider.length;
				var skip:uint = Math.max(0, l-maxPlots);
				
				for (i = skip; i < l; i++)
				{
					// If xField is empty, use the index as the x-axis value
					if (xField == "")
						xCoord = i-skip;
					else
						xCoord = dataProvider[i][xField];
					
					// If yField is empty, use the dataProvider value as the y-axis value
					if (yField == "")
						yCoord = dataProvider[i];
					else
						yCoord = dataProvider[i][yField];
					
					if (xCoord < xMinNew)
						xMinNew = xCoord;
					if (xCoord > xMaxNew)
						xMaxNew = xCoord;
					if (yCoord < yMinNew) {
						yMinNew = yCoord;
						private::posMinValue = i;
					}
					if (yCoord > yMaxNew) {
						yMaxNew = yCoord;
						private::posMaxValue = i;	
					}
				}
				
				if (xMinScaleMode == FIXED_SCALE)
					xMin = xMinFixed;
				else
					xMin = xMinNew;
				
				if (xMaxScaleMode == FIXED_SCALE)
					xMax = xMaxFixed;
				else
					xMax = xMaxNew;
				
				if (yMinScaleMode == FIXED_SCALE)
					yMin = yMinFixed;
				else
					yMin = yMinNew;
				
				if (yMaxScaleMode == FIXED_SCALE)
					yMax = yMaxFixed;
				else
					yMax = yMaxNew;
				
				protected::valueMin = yMinNew; 
				protected::valueMax = yMaxNew;
				
				_rangeDirty = false;
				invalidateDisplayList();
			}
		}	
		
		/**
		 *  Computes and returns the padding to be applied around the sparkline chart.
		 *  
		 *  <p>Padding is used when computing horizontal and vertical scale factors used
		 *  to convert data points in pixel positions.</p>
		 * 
		 *  <p>Note: Method needs to be overriden when additional padding elements must be
		 *  considered.</p>
		 * 
		 *  @return Padding value in pixels.
		 */
		protected function getPadding():Number {
			return getStyle("padding");
		}
		
		/**
		 *  Update scaling factor used to convert data values and pixels on the x-axis.
		 * 
		 *  @param unscaledWidth Width to be used to compute the horizontal scaling factor.
		 */
		protected function updateHorizontalScale(unscaledWidth:Number):void {
			if (!dataProvider)
				return;
			
			if (_horizontalScaleDirty) {
				var nb:Number = xMax - xMin;
				var w:Number = unscaledWidth - (2 * getPadding());
				if (nb != 0)
					_horizontalScale = Math.abs(w / nb);
				else
					_horizontalScale = w;
				
				_horizontalScaleDirty = false;
			}
		}
		
		/**
		 *  Update scaling factor used to convert data values and pixels on the y-axis.
		 * 
		 *  @param height Height to be used to compute the vertical scaling factor.
		 */
		protected function updateVerticalScale(unscaledHeight:Number):void {
			if (!dataProvider)
				return;
			
			if (_verticalScaleDirty) {
				var div:Number = yMax - yMin;
				var h:Number = unscaledHeight - (2 * getPadding());
				if (div != 0)
					_verticalScale = Math.abs(h / div);
				else
					_verticalScale = h;
				
				_verticalScaleDirty = false;
			}
		}
		
		/**
		 *  @private
		 *  If our width or height has changed, scaling factors must be updated.
		 */
		private function manageResize(unscaledWidth:Number, unscaledHeight:Number):void {
			if (_lastWidth != unscaledWidth) {
				_horizontalScaleDirty = true;
			}
			if (_lastHeight != unscaledHeight) {
				_verticalScaleDirty = true;
			}
			_lastWidth = unscaledWidth;
			_lastHeight = unscaledHeight;
		}
		
		/**
		 *  Draws the Sparkline chart.
		 * 
		 *  @param unscaledWidth Width of the chart area.
		 *  @param unscaledHeight Height of the chart area.
		 * 
		 *  <p>Note: To be implemented in subclasses.</p>
		 */
		protected function drawChart(unscaledWidth:Number, unscaledHeight:Number):void
		{
		}
		
		/**
		 *  Draws the background for the component.
		 * 
		 *  @param unscaledWidth Width of the background.
		 *  @param unscaledHeight Height of the background.
		 */
		protected function drawBackground(unscaledWidth:Number, unscaledHeight:Number):void
		{
			graphics.beginFill(getStyle("backgroundColor"), getStyle("backgroundAlpha"));
			graphics.drawRect(0, 0, unscaledWidth, unscaledHeight);
			graphics.endFill();
		}
		
		private var _position:Point = new Point();

		/**
		 *  Storage for data point to pixel conversions.
		 *  <p>Note: Kept as a variable for performance reasons, filled by <code>computePosition()</code>.</p>
		 */
		protected function get position():Point
		{
			return _position;
		}

		/**
		 * @private
		 */
		protected function set position(value:Point):void
		{
			_position = value;
		}

		
		/**
		 *  Computes position in pixel of a data point.
		 * 
		 *  @param index Index of the data point in the dataProvider.
		 *  @param skip Number of elements of the dataProvider to be skipped.
		 *  @param unscaledWidth Width of the sparkline chart area.
		 *  @param unscaledHeight Height of the sparkline chart area.
		 *  @param padding Padding to consider around the sparkline chart.
		 */
		protected function computePosition(index:uint, skip:uint, unscaledWidth:Number, unscaledHeight:Number, padding:Number):void {
			if (xField == "")
				position.x = index-skip;
			else
				position.x = dataProvider[index][xField];
			position.x = getXPosition(position.x, unscaledWidth, padding);
			
			if (yField == "")
				position.y = dataProvider[index];
			else
				position.y = dataProvider[index][yField];
			position.y = getYPosition(position.y, unscaledHeight, padding);
		}
		
		/**
		 *  Returns the y position of value based on height and padding.
		 *
		 *  @param value Data value to convert in pixel position.  
		 *  @param chartHeight Height available for the sparkline chart.  
		 *  @param padding Padding to consider around the sparkline chart.
		 * 
		 *  @return Position on y-axis.
		 */
		protected function getYPosition(value:Number, chartHeight:Number, padding:Number):Number {
			var pos:Number;
			
			pos = value-yMin;
			pos = (pos * (-_verticalScale)) + chartHeight-(2*padding);
			pos = pos + padding;
			
			// Clipping			
			pos = Math.min(pos, chartHeight-padding);
			pos = Math.max(pos, padding);
			
			return pos;
		}
		
		/**
		 *  Returns the x position of value based on width and padding.
		 *
		 *  @param value Data value to convert in pixel position.  
		 *  @param chartWidth Width available for the sparkline chart.  
		 *  @param padding Padding to consider around the sparkline chart.
		 * 
		 *  @return Position on x-axis.
		 */
		protected function getXPosition(value:Number, chartWidth:Number, padding:Number):Number {
			var pos:Number;
			
			pos = value - xMin;
			pos = pos * _horizontalScale;
			pos = pos + padding;
			
			// Clipping			
			pos = Math.min(pos, chartWidth-padding);
			pos = Math.max(pos, padding);
			
			return pos;
		}
		
		/**
		 *  @private
		 *  Indicates whether the toolTip is currently visible.
		 */
		private var _toolTipVisible:Boolean;
		
		/**
		 *  @private
		 *  Latest TOOL_TIP_SHOW received.
		 */
		private var _toolTipEvent:ToolTipEvent;
		
		/**
		 *  Update toolTip value (only) if it is visible.
		 * 
		 *  <p>If the toolTip is not visible, do nothing. But if it is, 
		 *  (re)fire a TOOL_TIP_SHOW event to refresh it.</p>
		 */
		protected function updateToolTip():void {
			if (_toolTipVisible)
				toolTipShowHandler(_toolTipEvent);
		}
		
		/**
		 *  ToolTip is not visible anymore.
		 * 
		 *  @param event ToolTipEvent.TOOL_TIP_HIDE event fired just before the toolTip be hidden.
		 */
		protected function toolTipHideHandler(event:ToolTipEvent):void {
			_toolTipVisible = false;
		}
		
		/**
		 *  Update tooltip content.
		 * 
		 *  @param event ToolTipEvent.TOOL_TIP_SHOW event fired just before the toolTip be shown.
		 */
		protected function toolTipShowHandler(event:ToolTipEvent):void {
			// Keep the event such that we can trigger a fake SHOW if we need to refresh
			// the toolTip while it is visible.
			_toolTipVisible = true;
			_toolTipEvent = event;
			
			// If toolTip value has not changed, rely on current toolTip value
			if (!_toolTipDirty)
				return;
			
			// Else, determine the toolTip value, and assign it to both the toolTip property and
			// the text property of the ToolTip object provided in the event.
			if (showToolTip) {
				if (toolTipFunction == null) {
					toolTipFunction = defaultToolTipFunction;
				}
				
				if (toolTipFunction != null) {
					var v:Number;
					if (yField == "")
						v = dataProvider.length-1;
					else
						v = dataProvider[dataProvider.length-1][yField];
					
					event.toolTip.text = toolTipFunction(this, v);
					toolTip = event.toolTip.text;
				}
			}
			else {
				event.toolTip.text = "";
				toolTip = "";
			}
			_toolTipDirty = false;
		}
		
		/**
		 *  Formats the toolTip associated to SparklineChart.
		 *  
		 *  <p>Default toolTip is defined as:</p>
		 *  <pre>
		 *  	&lt;<code>toolTipHeader</code>&gt;
		 * 		&lt;<code>toolTipValueHeader</code>&gt;: &lt;value&gt; 	    	(&lt;value&gt; in red when above threshold [when defined])
		 * 		[Min: &lt;min&gt;, Max: &lt;max&gt;]		(displayed in italic, &lt;min&gt;, &lt;max&gt; in red when above threshold [when defined])
		 * 		&lt;<code>toolTipThresholdHeader</code>&gt;: &lt;threshold&gt;	(displayed in italic, when threshold defined)
		 *  </pre>
		 * 
		 *  <p>Note: <code>defaultToolTipFunction</code> is used only if <code>toolTipFunction</code> is not defined.</p>
		 * 
		 *  @param sparklineChart Reference to the concerned SparklineChart (for custom toolTipFunction)
		 *  @param value Last value displayed by the SparklineChart.
		 * 
		 *  @return Text to display in the toolTip.
		 */
		protected function defaultToolTipFunction(sparklineChart:SparklineChart, value:Number):String {
			var res:String="";
			
			if (toolTipHeader != "")
				res += "<b><font size='+1'>"+toolTipHeader+ "</font></b><br>";
			
			res += "<font size='+1'><b>"+toolTipValueHeader+"</b>";
			res += formatValue(value)+"</font>";
			
			res += "<br><i><font size='+1'>[Min: ";
			res += formatValue(public::valueMin);
			
			res += ", Max: ";
			res += formatValue(public::valueMax);
			
			res += "]</font></i>";	
			
			if (showThreshold) {
				res += "<br><i><font size='+1'>["+toolTipThresholdHeader;
				res += formatValue(threshold, false);
				res += "]</font></i>";	
			}
			
			return res;
		}  		
		
		/**
		 *  @private
		 *  Formats a value and render it in red & bold when above the threshold.
		 * 
		 *  @param value Value to format.
		 *  @param color Indicates whether the value must be colored based on threshold.
		 * 
		 *  @return Text with HTML tags representing the formatted value.
		 */
		private function formatValue(value:Number, color:Boolean=true):String {
			var res:String = "";
			
			var colored:Boolean;
			colored = color && showThreshold && ((upperIsFault && value >= threshold) || (!upperIsFault && value <= threshold));
			
			if (colored)
				res += "<font color=\"#FF0000\"><b>";
			
			if (toolTipNumberFormatter)
				res += toolTipNumberFormatter.format(value);
			else
				res += value;
			
			if (colored)
				res += "</b></font>";
			
			return res;
		}
		
		/**
		 *  Assigns the custom sort function when relevant.
		 * 
		 *  <p>Defines a custom sort function when yField is defined and we're used as an 
		 *  itemRenderer in a DataGrid. The custom sorting function is assigned to the
		 *  corresponding column of the DataGrid.</p>
		 *  
		 *  <p>Note: This is automatically called when setting <code>yField</code> or <code>listData</code>
		 *  properties.</p>
		 */
		protected function setSortFunction():void {
			if (_listData &&
				_listData is DataGridListData &&
				DataGridListData(_listData).dataField) 
			{
				// When used as an itemRenderer in a DataGrid, if yField is defined, we need
				// a custom sort function.
				if (yField != "") {                
					var dataField:String = DataGridListData(_listData).dataField;
					
					if (DataGridListData(_listData).owner is AdvancedDataGridBaseEx) {
						var adg:AdvancedDataGridBaseEx = AdvancedDataGridBaseEx(DataGridListData(_listData).owner);
						adg.columns[DataGridListData(_listData).columnIndex].sortCompareFunction=sortSparkline(dataField,yField);
					}
					else if (DataGridListData(_listData).owner is DataGrid) {
						var dg:DataGrid = DataGrid(DataGridListData(_listData).owner);
						dg.columns[DataGridListData(_listData).columnIndex].sortCompareFunction=sortSparkline(dataField,yField);
					}
				}
			}
		}
		
		/**
		 *  Custom sort function.
		 * 
		 *  <p>To be used for sorting in datagrid when sparkline is used as an item renderer in a datagrid
		 *  and an yField is defined (because, in such a case, the property associated to the sparkline
		 *  data is an ArrayCollection in which we need to pick the value for sorting).</p>
		 * 
		 *  <p>Use sortCompareFunction="SparklineChart.sortSparkline('dataField','yField')"</p>
		 *  
		 *  <p>Note: This custom sort function is automatically defined when <code>yField</code>
		 *  property is set.</p>
		 * 
		 *  @param dataField Name of the property in the data provider associated with the column.  
		 *  @param yField Name of the property in the ArrayCollection identified by dataField that contains sparkline data.  
		 */
		public static function sortSparkline(dataField:String, yField:String):Function
		{
			return function(obj1:Object, obj2:Object):int 
			{
				var o1:ArrayCollection = obj1[dataField];
				var o2:ArrayCollection = obj2[dataField];
				if (o1.length==0 || o2.length==0)
					return -1;
				return ObjectUtil.numericCompare(o1[o1.length-1][yField],o2[o2.length-1][yField]);
			} 
		}		
	}
}
